package org.zjvis.datascience.service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.zjvis.datascience.common.constant.NoticeConstant;
import org.zjvis.datascience.common.dto.DatasetDTO;
import org.zjvis.datascience.common.dto.DatasetProjectDTO;
import org.zjvis.datascience.common.dto.ProjectDTO;
import org.zjvis.datascience.common.dto.ProjectDatasetDTO;
import org.zjvis.datascience.common.dto.TaskDTO;
import org.zjvis.datascience.common.dto.UserProjectDTO;
import org.zjvis.datascience.common.dto.user.UserDTO;
import org.zjvis.datascience.common.enums.NoticeTypeEnum;
import org.zjvis.datascience.common.enums.ProjectRoleAuthEnum;
import org.zjvis.datascience.common.vo.dataset.QueryDatasetUsedInProjectVO;
import org.zjvis.datascience.common.widget.dto.WidgetDTO;
import org.zjvis.datascience.common.widget.enums.WidgetTypeEnum;
import org.zjvis.datascience.common.exception.BaseErrorCode;
import org.zjvis.datascience.common.exception.DataScienceException;
import org.zjvis.datascience.common.util.JwtUtil;
import org.zjvis.datascience.common.util.StringUtil;
import org.zjvis.datascience.common.vo.project.LoadDatasetVO;
import org.zjvis.datascience.common.vo.project.ProjectDataSourceTableVO;
import org.zjvis.datascience.common.vo.project.ProjectDataSourceVO;
import org.zjvis.datascience.common.vo.project.ProjectDatasetVO;
import org.zjvis.datascience.service.mapper.DatasetMapper;
import org.zjvis.datascience.service.mapper.DatasetProjectMapper;
import org.zjvis.datascience.service.mapper.ProjectMapper;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;

/**
 * @description DatasetProject 项目数据集 Service
 * @date 2021-12-27
 */
@Service
public class DatasetProjectService {

    private final static Logger logger = LoggerFactory.getLogger("DatasetProjectService");

    @Autowired
    private DatasetProjectMapper datasetProjectMapper;

    @Autowired
    private DatasetMapper datasetMapper;

    @Autowired
    private ProjectMapper projectMapper;

    @Lazy
    @Autowired
    private NoticeService noticeService;

    @Lazy
    @Autowired
    private UserProjectService userProjectService;

    @Lazy
    @Autowired
    private ProjectService projectService;

    @Autowired
    private WidgetService widgetService;

    @Lazy
    @Autowired
    private TaskService taskService;

    @Autowired
    private UserService userService;

    @Autowired
    private DashboardService dashboardService;

    public List<DatasetProjectDTO> queryByDatasetId(Long datasetId) {
        return datasetProjectMapper.queryByDatasetId(datasetId);
    }

    /**
     * @param vo vo中项目Id不能为空，数据集id列表为null的话则删所有该项目数据
     */
    public void delete(LoadDatasetVO vo) {
        datasetProjectMapper.delete(vo.getProjectId(), vo.getDatasetIds());
        if (vo.getDatasetIds() != null && !vo.getDatasetIds().isEmpty()) {
            List<DatasetProjectDTO> dtos = toDto(vo, false);
            sendNotice(vo.getProjectId(), NoticeConstant.PROJECT_DATA_SOURCE_DELETE_TITLE,
                NoticeConstant.PROJECT_DATA_SOURCE_DELETE_CONTENT, dtos);
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public void add(LoadDatasetVO vo, Boolean needForce) {
        List<DatasetProjectDTO> dtos = toDto(vo, needForce);
        add(dtos);
        sendNotice(vo.getProjectId(), NoticeConstant.PROJECT_DATA_SOURCE_ADD_TITLE,
            NoticeConstant.PROJECT_DATA_SOURCE_ADD_CONTENT, dtos);
    }

    private List<DatasetProjectDTO> toDto(LoadDatasetVO vo,Boolean needForce) {
        Set<Long> datasetIds = vo.getDatasetIds();

        List<DatasetProjectDTO> res = new ArrayList<>();
        for (Long datasetId : datasetIds) {
            DatasetDTO ds = datasetMapper.queryById(datasetId);
            if (ds != null){
                if (needForce){
                    DatasetDTO cloneDs = ds.copyOne(JwtUtil.getCurrentUserId());
                    try {
                        datasetMapper.save(cloneDs);
                    }catch (Exception e){
                        //发生异常，一般是因为用户再次复制过相同的项目
                        cloneDs = ds;
                    }
                    res.add(DatasetProjectDTO.builder().datasetId(cloneDs.getId()).datasetName(ds.getName())
                            .projectId(vo.getProjectId()).build());
                } else if (ds.getUserId().equals(JwtUtil.getCurrentUserId())) {
                    res.add(DatasetProjectDTO.builder().datasetId(datasetId).datasetName(ds.getName())
                            .projectId(vo.getProjectId()).build());
                }
            }
        }
        if (needForce) {
            if (CollectionUtils.isEmpty(res)) {
                throw new DataScienceException(BaseErrorCode.DATASET_NOT_AVAILABLE);
            }
        }
        return res;
    }

    public void add(List<DatasetProjectDTO> dtos) {
        if (dtos.size()> 0) {
            try {
                datasetProjectMapper.batchInsert(dtos);
            } catch (DuplicateKeyException e) {
                throw new DataScienceException(BaseErrorCode.DUPLICATED_DATA, e.getCause().getMessage(),
                        null);
            }
        }
    }

    /**
     * 获取项目数据源列表 以及pipeline中的节点表
     *
     * @param projectId
     * @return
     */
    public List<ProjectDataSourceVO> queryAllDataset(Long projectId, Long pipelineId, String askedDatasetName) {
        //未指定pipelineId 将默认使用 创建project时 最先创建的pipeline
        if (null == pipelineId) pipelineId = projectService.getDefaultPipelineId(projectId);
        List<ProjectDatasetDTO> pdds = datasetProjectMapper.queryProjectAllTables(projectId, pipelineId, askedDatasetName);
        List<ProjectDataSourceVO> res = new ArrayList<>();
        List<ProjectDataSourceTableVO> datasets = new ArrayList<>();
        List<ProjectDataSourceTableVO> pipelineNodes = new ArrayList<>();
        //同一个用户的数据整理到一起
        for (ProjectDatasetDTO pdd : pdds) {
            String userName = pdd.getUserName();
            if (StringUtils.isBlank(userName)) {
                continue;
            }

            //提取出表名
            ProjectDataSourceTableVO pdst = ProjectDataSourceTableVO.builder()
                    .id(pdd.getId())
                    .name(pdd.getDatasetName())
                    .categoryName(pdd.getCategoryName())
                    .build();

            JSONObject dataJson = JSONObject.parseObject(pdd.getDataJson());
            if (dataJson.containsKey("out_table_rename")) {
                pdst.setTableName(dataJson.getString("out_table_rename") + dataJson.getString("lastTimeStamp"));
                pdst.setType(WidgetTypeEnum.TASK.getDesc());
            }else {
                if (dataJson.containsKey("output")){
                    JSONArray outputArray = dataJson.getJSONArray("output");
                    if (outputArray.size() > 0) {
                        JSONObject output = outputArray.getJSONObject(0);
                        if (output.getString("tableName").endsWith("_")) {
                            pdst.setTableName(output.getString("tableName") + dataJson.getString("lastTimeStamp"));
                        } else if (!output.getString("tableName").contains(".")) {
                            pdst.setTableName("dataset." + output.getString("tableName"));
                        }else{
                            pdst.setTableName(output.getString("tableName"));
                        }
                        pdst.setType(WidgetTypeEnum.TASK.getDesc());
                    }
                }else {
                    pdst.setTableName(dataJson.getString("schema") + "." + dataJson.getString("table"));
                    pdst.setType(WidgetTypeEnum.DATASET.getDesc());
                }
            }

            if (pdd.getCategoryName().equals("pipeline")){
                pipelineNodes.add(pdst);
            }else {
                if (pdd.getUserId() != JwtUtil.getCurrentUserId()) {
                    pdst.setName(pdst.getName() + " (来自外部协作者" + pdd.getUserName() + ")");
                }
                datasets.add(pdst);
            }

        }
        res.add(0, new ProjectDataSourceVO("", datasets, pipelineNodes));
        return res;
    }

    public List<ProjectDatasetDTO> queryProjectDataset(Long projectId, String searchedName){
        return datasetProjectMapper.queryProjectDataset(projectId, searchedName);
    }

    /**
     * 获取项目数据源列表
     *
     * @param vo
     * @return
     */
    public List<ProjectDataSourceVO> queryProjectDataset(ProjectDatasetVO vo) {
        List<ProjectDatasetDTO> pdds = this.queryProjectDataset(vo.getProjectId(), vo.getDatasetName());
        List<ProjectDataSourceVO> res = new ArrayList<>();
        long currentUserId = JwtUtil.getCurrentUserId();
        
        //获取项目成员信息
        List<UserProjectDTO> userProjects = userProjectService.query(vo.getProjectId());
        List<Long> projectMembers = new ArrayList<>();
        for (UserProjectDTO userProject : userProjects) {
            //获取权限超访客的用户id
            if (userProject.getRoleId() <ProjectRoleAuthEnum.VISITOR.getValue()) {
                projectMembers.add(userProject.getUserId());
            }
        }

        //同一个用户的数据整理到一起
        for (ProjectDatasetDTO pdd : pdds) {
            String userName = pdd.getUserName();
            if (StringUtils.isBlank(userName) || !projectMembers.contains(pdd.getUserId())) {
                continue;
            }

            //提取出表名
            ProjectDataSourceTableVO pdst = ProjectDataSourceTableVO.builder()
                .id(pdd.getId())
                .name(pdd.getDatasetName())
                .categoryName(pdd.getCategoryName())
                .build();

            try {
                JSONObject dataJson = JSONObject.parseObject(pdd.getDataJson());
                pdst.setTableName(dataJson.getString("schema") + "." + dataJson.getString("table"));
                pdst.setType(dataJson.getString("type"));
            } catch (Exception e) {

            }
            //第一次处理
            if (res.isEmpty()) {
                ProjectDataSourceVO pds = new ProjectDataSourceVO();
                pds.setUserName(userName);
                List<ProjectDataSourceTableVO> list = new ArrayList<>();
                list.add(pdst);
                pds.setDataset(list);
                if (pdd.getUserId() == currentUserId) {
                    res.add(0, pds);
                } else {
                    res.add(pds);
                }
                continue;
            }

            boolean flag = true;
            //遍历已经添加的用户
            for (ProjectDataSourceVO pd : res) {
                if (userName.equals(pd.getUserName())) {
                    pd.getDataset().add(pdst);
                    flag = false;
                    break;
                }
            }
            //已添加用户未匹配到，添加新用户
            if (flag) {
                ProjectDataSourceVO pds = new ProjectDataSourceVO();
                pds.setUserName(userName);
                List<ProjectDataSourceTableVO> list = new ArrayList<>();
                list.add(pdst);
                pds.setDataset(list);
                if (pdd.getUserId() == currentUserId) {
                    res.add(0, pds);
                } else {
                    res.add(pds);
                }
            }
        }
        return res;
    }

    public List<DatasetProjectDTO> selectCountByProjectAndUser(Long projectId, Long userId) {
        return datasetProjectMapper.queryByProjectAndUser(projectId, userId);
    }

    public void sendNotice(Long projectId, String title, String content,
        List<DatasetProjectDTO> dtos) {
        try {
            Set<Long> receivers = userProjectService.getReceiver(projectId, new HashSet<Long>() {{
                    add(JwtUtil.getCurrentUserId());
                }},
                new HashSet<Integer>() {{
                    add(ProjectRoleAuthEnum.VISITOR.getValue());
                }});
            if (receivers == null || receivers.isEmpty()) {
                return;
            }

            ProjectDTO project = projectMapper.selectByPrimaryKey(projectId);
            if (project == null) {
                return;
            }

            List<String> names = new ArrayList<>();
            for (DatasetProjectDTO dto : dtos) {
                names.add(dto.getDatasetName());
            }
            if (names.isEmpty()) {
                return;
            }

            content = String
                .format(content,
                    StringUtil.addHtmlBoldLabel(JwtUtil.getCurrentUserDTO().getName()),
                    StringUtil.addHtmlBoldLabel(project.getName()),
                    StringUtil.addHtmlBoldLabel(names.toString()));

            JSONObject process = new JSONObject();
            process.put("projectId", projectId);
            process.put("userId", JwtUtil.getCurrentUserId());
            noticeService.saveAndSendToUser(receivers, title, content, process.toJSONString(),
                NoticeTypeEnum.project.getType(), JwtUtil.getCurrentUserId());
        } catch (Exception e) {
            logger.error("协作者数据源更新，消息发送失败", e);
        }
    }

    public List<ProjectDatasetDTO> queryByProjectId(Long projectId) {
        return datasetProjectMapper.queryProjectDataset(projectId, "");
    }

    /**
     * 获取数据集在项目中的使用信息
     * 
     * @param vo
     * @return
     */
    public Object queryDatasetUsedInfo(QueryDatasetUsedInProjectVO vo) {
        vo.setUserId(JwtUtil.getCurrentUserId());
        //List<DatasetUsedInProjectDTO> duips = datasetProjectMapper.queryDatasetUsedInProject(vo);
        List<Map<String,Object>> usersInfo = new ArrayList();

        Set<Long> userIds = new HashSet<>();

        try {
            userIds = getDatasetUsedInProjectUser(null, vo.getDatasetId());
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }

        //移除用户自身的使用
        userIds.remove(vo.getUserId());

        if (!userIds.isEmpty()) {
            //获取用户信息
            List<UserDTO> users = userService.listByIds(userIds);

            for (UserDTO user : users) {
                Map<String,Object> userInfo2 = new HashMap<>(1);
                userInfo2.put("userId", user.getId());
                userInfo2.put("userName", user.getName());
                usersInfo.add(userInfo2);
            }
        }
        return usersInfo;
    }
               
    /**
     * 删除指定项目指定用户的数据源信息
     * 
     * @param projectId
     * @param userId
     * @return
     */
    public int  deleteByProjectAndUser(Long projectId, Long userId) {
        return datasetProjectMapper.deleteByProjectAndUser(projectId, userId);
    }

    /**
     * 获取指定数据集在指定project中的使用人id
     * 
     * @param projectId 为null时，检查所有project
     * @param datasetId
     * @return
     * @throws Exception
     */
    public Set<Long> getDatasetUsedInProjectUser (Long projectId, Long datasetId)
        throws Exception {
        Set<Long> res = new HashSet<>();
        JSONObject jo = getDatasetUsedInProjectUser(projectId, datasetId, false, null, null);
        if (!jo.isEmpty()) {
            if (jo.get("widget") != null) {
                Set<Long> widget = (Set<Long>) jo.get("widget");
                res.addAll(widget);
            }
            if (jo.get("task") != null) {
                Set<Long> task = (Set<Long>) jo.get("task");
                res.addAll(task);
            }
        }
        return res;
    }

    /**
     * 判断当前用户数据集在project中的使用情况
     * 
     * @param projectId
     * @param datasetId
     * @return
     * @throws Exception
     */
    public JSONObject checkCurrentUserDatasetUsedInProject (Long projectId, Long datasetId)
        throws Exception {
        return getDatasetUsedInProjectUser(projectId, datasetId, true, 2,
            JwtUtil.getCurrentUserId());
    }

    /**
     * 获取指定数据集在指定project中的使用人id 或 断当前用户数据集在project中的使用情况
     * 
     * @param projectId
     * @param datasetId
     * @param check
     * @param checkType
     * @param checkUser
     * @return
     * @throws Exception
     */
    public JSONObject getDatasetUsedInProjectUser(Long projectId, Long datasetId, boolean check,
        Integer checkType, Long checkUser) throws Exception {
        JSONObject res = new JSONObject();
        List<WidgetDTO> widgets = widgetService.queryTemplateByDatasetId(datasetId);
//        long currentUserId = JwtUtil.getCurrentUserId();

        if (!CollectionUtils.isEmpty(widgets) && dashboardService.containsWidget(widgets, projectId)) {
            Set<Long> uIds = new HashSet<>();
            for (WidgetDTO widget : widgets) {
                if (check) {
                    if (checkType == null || checkUser == null) {
                        return res;
                    }
                    switch(checkType) {
                        case 1:
                            // 
                            if (checkUser.equals(widget.getGmtCreator())) {
                                continue;
                            } else {
                                throw new Exception("该数据集已被可视化视图图表使用，无法删除，请先在pipeline中删除使用该数据的节点!");
                            }
                        case 2:
                            if (checkUser.equals(widget.getGmtCreator())) {
                                //只有自己使用才需要拦截
                                throw new Exception("该数据集已被可视化视图图表使用，无法删除，请先在pipeline中删除使用该数据的节点!");
                            }
                            break;
                        default:
                            throw new Exception("该数据集已被可视化视图图表使用，无法删除，请先在pipeline中删除使用该数据的节点!");
                    }
//                    if (!onlyCheckCurrentUser) {
//                        throw new Exception("该数据集已被可视化视图图表使用，无法删除，请先在pipeline中删除使用该数据的节点!");
//                    } else if (onlyCheckCurrentUser && currentUserId == widget.getGmtCreator().longValue()) {
//                        //只有自己使用才需要拦截
//                        throw new Exception("该数据集已被可视化视图图表使用，无法删除，请先在pipeline中删除使用该数据的节点!");
//                    }
                } else {
                    uIds.add(widget.getGmtCreator());
                }
            }
            res.put("widget", uIds);
        }
        //判断本项目下是否有依赖数据集的task
        List<TaskDTO> tasks = taskService.queryByDatasetId(datasetId, projectId);
        if (!CollectionUtils.isEmpty(tasks)) {
            Set<Long> uIds = new HashSet<>();
            for (TaskDTO task : tasks) {
                if (check) {
                    if (checkType == null || checkUser == null) {
                        return res;
                    }
                    switch(checkType) {
                        case 1:
                            // 
                            if (checkUser.equals(task.getUserId())) {
                                continue;
                            } else {
                                throw new Exception("该数据集已被可视化视图图表使用，无法删除，请先在pipeline中删除使用该数据的节点!");
                            }
                        case 2:
                            if (checkUser.equals(task.getUserId())) {
                                //只有自己使用才需要拦截
                                throw new Exception("该数据集已被可视化视图图表使用，无法删除，请先在pipeline中删除使用该数据的节点!");
                            }
                            break;
                        default:
                            throw new Exception("该数据集已被可视化视图图表使用，无法删除，请先在pipeline中删除使用该数据的节点!");
                    }
//                    if (!onlyCheckCurrentUser) {
//                        throw new Exception("该数据集已被可视化视图图表使用，无法删除，请先在pipeline中删除使用该数据的节点!");
//                    } else if (onlyCheckCurrentUser && currentUserId == task.getUserId().longValue()
//                        || !onlyCheckCurrentUser) {
//                        //只有自己使用才需要拦截
//                        throw new Exception("该数据集已被可视化视图图表使用，无法删除，请先在pipeline中删除使用该数据的节点!");
//                    }
                } else {
                    uIds.add(task.getUserId());
                }
            }
            res.put("task", uIds);
        }
        return res;
    }

    public boolean userDatasetUsedCheck (Long projectId, Long userId){
        List<DatasetProjectDTO> dtos = datasetProjectMapper.queryByProjectAndUser(projectId, userId);
        for (DatasetProjectDTO dto : dtos) {
            try {
                getDatasetUsedInProjectUser(projectId, dto.getDatasetId(), true, 1, userId);
            } catch (Exception e) {
                logger.warn(e.getMessage(),e);
                return true;
            }
        }
        return false;
    }
}
